package pingsource

import (
    "context"
    "encoding/json"
    "errors"
    "fcs-api/src/app/model"
    k8s "fcs-api/src/client/kubernetes"
    cloudevents "github.com/cloudevents/sdk-go/v2"

    "k8s.io/apimachinery/pkg/apis/meta/v1"
    duckv1 "knative.dev/pkg/apis/duck/v1"

    "fmt"

    "strings"

    "git.inspur.com/x/common/loggerfactory"
    ev1 "knative.dev/eventing/pkg/apis/eventing/v1"
    sourcev1 "knative.dev/eventing/pkg/apis/sources/v1"
    "knative.dev/pkg/apis"
)

var logger = loggerfactory.GetLogger()

func CreatePingsource(fcClient *k8s.FcClient, namespace string, trigger *model.Trigger) error {

    schedule, err := getPingSourceConfig(trigger.Config)
    if err != nil {
        triggerStr, _ := json.Marshal(trigger)
        logger.Error("CreatePingsource error, date format not right, trigger is %s", string(triggerStr))
        return err
    }

    name := trigger.FuncName + "-" + trigger.Name
    ps := &sourcev1.PingSource{
        ObjectMeta: v1.ObjectMeta{
            Name: name,
            Annotations: map[string]string{
                "auth.type":              "iam-client",
                "auth.iam.url":           model.Base.KeycloakConfig.Host + "/realms/picp/protocol/openid-connect/token",
                "auth.iam.client.id":     model.Base.KeycloakConfig.ClientId,
                "auth.iam.client.secret": model.Base.KeycloakConfig.ClientSecret,
            },
        },
        Spec: sourcev1.PingSourceSpec{
            Schedule:    schedule,
            Data:        getPingSourceData(trigger.Param),
            ContentType: cloudevents.ApplicationJSON,
            SourceSpec: duckv1.SourceSpec{
                Sink: duckv1.Destination{
                    URI: &apis.URL{
                        Scheme: "http",
                        Host:   "kong-proxy." + model.Base.GatewayNamespace,
                        Path:   fmt.Sprintf("/async/%s/%s/%s.%s", trigger.A5tId, trigger.GroupName, trigger.FuncName, trigger.TargetName),
                    },
                },
            },
        },
    }
    t := &ev1.Trigger{
        ObjectMeta: v1.ObjectMeta{
            Name: name,
        },
        Spec: ev1.TriggerSpec{
            Broker: model.DefaultBroker,
            Filter: &ev1.TriggerFilter{
                Attributes: ev1.TriggerFilterAttributes{
                    "source": "/apis/v1/namespaces/" + namespace + "/pingsources/" + name,
                },
            },
            Subscriber: duckv1.Destination{
                Ref: &duckv1.KReference{
                    APIVersion: "v1",
                    Kind:       "Service",
                    Namespace:  namespace,
                    Name:       getSvcName(trigger),
                },
            },
        },
    }

    _, err = fcClient.CreatePingsource(namespace, ps)
    if err != nil {
        _ = logger.Error("Failed to create PingSources: %s", err)
        return err
    }
    _, err = fcClient.CreateTrigger(namespace, t)
    if err != nil {
        _ = logger.Error("Failed to create Triggers: %s", err)
        return err
    }
    return nil
}

func UpdatePingsource(fcClient *k8s.FcClient, namespace string, trigger *model.Trigger) error {
    triggerName := trigger.FuncName + "-" + trigger.Name
    ps, err := GetPingsource(fcClient, namespace, triggerName)
    if err != nil {
        _ = logger.Error("Failed to Get PingSources: %s", err)
        return err
    }
    t, err := GetTrigger(fcClient, namespace, triggerName)
    if err != nil {
        _ = logger.Error("Failed to Get Triggers: %s", err)
        return err
    }

    schedule, err := getPingSourceConfig(trigger.Config)
    if err != nil {
        triggerStr, _ := json.Marshal(trigger)
        logger.Error("Update error, date format not right, trigger is %s", string(triggerStr))
        return err
    }

    ps.Spec.Schedule = schedule
    ps.Spec.Data = getPingSourceData(trigger.Param)
    ps.Spec.ContentType = cloudevents.ApplicationJSON
    ps.Spec.Sink.URI.Path = fmt.Sprintf("/async/%s/%s/%s.%s", trigger.A5tId, trigger.GroupName, trigger.FuncName, trigger.TargetName)
    _, err = fcClient.UpdatePingsource(namespace, ps)
    if err != nil {
        _ = logger.Error("Failed to Update PingSources: %s", err)
    }

    t.Spec.Subscriber.Ref.Name = getSvcName(trigger)
    _, err = fcClient.UpdateTrigger(namespace, t)
    if err != nil {
        _ = logger.Error("Failed to Update Triggers: %s", err)
    }

    return err
}

func DeletePingsource(fcClient *k8s.FcClient, namespace, triggerName string) error {

    err := fcClient.KEventing.SourcesV1().PingSources(namespace).Delete(context.TODO(), triggerName, v1.DeleteOptions{})
    if err != nil {
        _ = logger.Error("Failed to Delete PingSources: %s", err)
        return err
    }
    err = fcClient.KEventing.EventingV1().Triggers(namespace).Delete(context.TODO(), triggerName, v1.DeleteOptions{})
    if err != nil {
        _ = logger.Error("Failed to Delete Triggers: %s", err)
        return err
    }
    return nil
}

func GetPingsource(fcClient *k8s.FcClient, namespace, triggerName string) (*sourcev1.PingSource, error) {
    return fcClient.KEventing.SourcesV1().PingSources(namespace).Get(context.TODO(), triggerName, v1.GetOptions{})
}

func GetTrigger(fcClient *k8s.FcClient, namespace, triggerName string) (*ev1.Trigger, error) {
    return fcClient.KEventing.EventingV1().Triggers(namespace).Get(context.TODO(), triggerName, v1.GetOptions{})
}

func getSvcName(trigger *model.Trigger) string {
    svcName := ""
    if trigger.TargetType == 2 { //别名
        svcName = trigger.FuncName + "-" + trigger.TargetName
    } else { //版本
        svcName = trigger.FuncName
        if !strings.Contains(trigger.TargetName, "LATEST") {
            v := strings.Replace(trigger.TargetName, "?ping", "", -1)
            svcName = trigger.FuncName + "--v" + v
        }
    }
    return svcName
}

func getPingSourceConfig(config string) (string, error) {

    if strings.HasPrefix(config, "@every") {
        mintuesFormate := "*/%s * * * *"
        hourFormate := "* */%s * * *"
        configArrs := strings.Split(config, " ")
        if len(configArrs) != 2 {
            return "", errors.New("PingSource config error")
        }
        timeConfig := configArrs[1]
        if strings.Contains(timeConfig, "h") {
            timeStr := strings.TrimSuffix(timeConfig, "h")
            return fmt.Sprintf(hourFormate, timeStr), nil
        } else if strings.Contains(timeConfig, "m") {
            timeStr := strings.TrimSuffix(timeConfig, "m")
            return fmt.Sprintf(mintuesFormate, timeStr), nil
        } else {
            return "", errors.New("PingSource config error")
        }
    }
    return config, nil

}

func getPingSourceData(data string) string {
    if data == "" {
        return "{}"
    }
    return data
}
